/**
 * Copyright (c) 2022 az13js
 * new_program & new_program_server is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of
 * the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF
 * ANY KIND,EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 * 这是一个本地的用来执行用户输入命令的服务器。
 *
 * 编译：
 *
 * g++ -o new_program_server new_program_server.cpp -lpthread
 *
 * 执行：
 *
 * ./new_program_server
 *
 * 说明：
 *
 * 服务器会通过本地 /tmp/new_program_server.sock （可用环境变量 NEW_PROGRAM_SOCKET_FILE 指定）
 * 监听客户端的消息。然后起一条线程，执行客户端发送过来的命令行命令。
 * 服务器的调试日志 /new_program_server.sock.log （可用环境变量 NEW_PROGRAM_LOG_FILE 指定）
 *
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <cstdlib> // system()

#include <iostream>
#include <thread>

/* 下面几个头文件用于实现日志函数 */

#include <fstream>
#include <iomanip>
#include <ctime>
#include <chrono>
#include <mutex>

std::mutex fileLogMutex;
std::string fileLogName;

void fileLog(std::string log) {
    using std::chrono::system_clock;
    using std::chrono::milliseconds;
    using std::chrono::duration_cast;
    if (fileLogName == "-") {
        return;
    }
    fileLogMutex.lock();
    std::ofstream file(fileLogName, std::ofstream::app);
    if (file.is_open()) {
        auto time_point_now = system_clock::now();
        auto now_sec = system_clock::to_time_t(time_point_now); // now_sec只能精确到秒
        auto time_point_sec = system_clock::from_time_t(now_sec);
        auto diff_time = time_point_now - time_point_sec; // diff_time>0, 精确的时间和now_sec之间的差距，不足1秒的部分
        file << std::put_time(std::localtime(&now_sec), "%FT%T.");
        file << std::setw(3) << std::setfill('0') << duration_cast<milliseconds>(diff_time).count();
        file << std::put_time(std::localtime(&now_sec), "%z");
        file << " [new_program_server] " << log;
        file << std::endl;
    }
    fileLogMutex.unlock();
}

int main() {
    using namespace std;
    sockaddr_un socketAddress;
    string socketFile("/tmp/new_program_server.sock"); // 这里路径长度不能大于107字节

    auto envSocketFile = getenv("NEW_PROGRAM_SOCKET_FILE");
    if (nullptr != envSocketFile) {
        if (string(envSocketFile).length() > 107 || string(envSocketFile).empty()) {
            cout << "NEW_PROGRAM_SOCKET_FILE.length() > 107 or empty, use default" << endl;
        } else {
            socketFile.assign(envSocketFile);
        }
    }

    cout << "Unix socket: " << socketFile << endl;

    fileLogName = socketFile + ".log"; // 日志文件文件名

    auto envFileLogName = getenv("NEW_PROGRAM_LOG_FILE");
    if (nullptr != envFileLogName) {
        if (string(envFileLogName).empty()) {
            cout << "NEW_PROGRAM_LOG_FILE is empty, use default" << endl;
        } else {
            fileLogName.assign(envFileLogName);
        }
    }

    cout << "Log file: " << fileLogName << endl;

    auto socketId = socket(AF_UNIX, SOCK_STREAM, 0);
    if (-1 == socketId) {
        cout << "Call socket fail, function return " << socketId << endl;
        return 1;
    }

    socketAddress.sun_family = AF_UNIX;
    socketAddress.sun_path[socketFile.length()] = '\0';
    socketFile.copy(socketAddress.sun_path, socketFile.length());

    remove(socketFile.c_str()); // 尝试删除文件先，否则bing失败。这个操作适用于Ctrl+C结束，这种场景会遗留一个socketFile没有删除

    if (0 != bind(socketId, (sockaddr *)&socketAddress, sizeof(socketAddress))) {
        cout << "Call bind fail" << endl;
        return 1;
    }

    if (0 != listen(socketId, SOMAXCONN)) {
        cout << "Call listen fail" << endl;
        return 1;
    }

    cout << "Waiting for client connection" << endl;
    while (true) { // 接受请求阶段
        auto clientConnectSocket = accept(socketId, NULL, NULL); // clientConnectSocket 由子线程close
        if (-1 == clientConnectSocket) {
            cout << "Call accept, return -1" << endl;
            return 1;
        }

        cout << "A client initiated connection was received" << endl;
        // 创建新的线程并分离线程，处理请求
        thread([](int clientConnectSocket) {
            char buffer[1024];
            auto recvDataLength = recv(clientConnectSocket, buffer, 1023, 0);
            close(clientConnectSocket);
            if (recvDataLength <= 0) {
                fileLog(string("Error, received data with length of 0 bytes"));
                return;
            }

            if (recvDataLength > 1022) {
                fileLog(string("Error, the command length is greater than 1022 bytes"));
                return;
            }

            buffer[recvDataLength] = '\0';

            fileLog(string(buffer)); // 日志记录接受到的内容

            // 新建进程（同步调用）
            system(buffer);

        }, clientConnectSocket).detach();
    }

    return 0;
}

